`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: tocanemis
// 
// Create Date: 2022/03/19 15:52:05
// Design Name: 
// Module Name: easy_hash
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////

(* dont_touch="true" *)

module easy_hash#(
    parameter               SN  =   1,      //slot number
                            HW  =   10,     //hash width
                            DW  =   32      //data width
    )(
    //system signals
    input                   clk,
    input                   rst_n,

    //insert
    input                   insert_i,
    input   [DW-1:0]        insert_data_i,
    output                  insert_error_o,
    output                  insert_end_o,

    //delete
    input                   delete_i,
    input   [DW-1:0]        delete_data_i,
    output                  delete_error_o,
    output                  delete_end_o,

    //search port-a
    input                   search_a_i,
    input   [DW-1:0]        search_a_data_i,
    output                  search_a_exist_o,
    output                  search_a_end_o,

    //search port-b
    input                   search_b_i,
    input   [DW-1:0]        search_b_data_i,
    output                  search_b_exist_o,
    output                  search_b_end_o,

    //RAM interface
    //side A
    output                  ram_wea_o,
    output  [HW-1:0]        ram_addra_o,
    output  [(DW+1)*SN-1:0] ram_dina_o,
    input   [(DW+1)*SN-1:0] ram_douta_i,

    //side B
    output                  ram_web_o,
    output  [HW-1:0]        ram_addrb_o,
    output  [(DW+1)*SN-1:0] ram_dinb_o,
    input   [(DW+1)*SN-1:0] ram_doutb_i
    );

    //------------Declare Signals----------------
    //register
    reg                     insert_r;
    reg                     insert_rr;

    reg                     delete_r;
    reg                     delete_rr;

    reg                     search_a_r;
    reg                     search_a_rr;

    reg                     search_b_r;
    reg                     search_b_rr;

    //hash calculate
    wire                    hash_a_valid;
    wire    [DW-1:0]        hash_a_data;
    reg     [DW-1:0]        hash_a_data_r;
    reg     [DW-1:0]        hash_a_data_rr;
    wire                    hash_a_result_valid;
    wire    [HW-1:0]        hash_a_result;
    reg     [HW-1:0]        hash_a_result_r;

    wire                    hash_b_valid;
    wire    [DW-1:0]        hash_b_data;
    reg     [DW-1:0]        hash_b_data_r;
    reg     [DW-1:0]        hash_b_data_rr;
    wire                    hash_b_result_valid;
    wire    [HW-1:0]        hash_b_result;

    //search hash table
    wire    [HW-1:0]        ram_rd_addra;
    wire    [HW-1:0]        ram_rd_addrb;
    reg                     ram_rd_dataa_valid;
    reg                     ram_rd_datab_valid;

    wire    [SN-1:0]        blank_slot;
    wire    [SN-1:0]        min_blank_slot;
    wire    [2*SN-1:0]      double_blank_slot;
    wire    [2*SN-1:0]      double_blank_slot_temp;

    wire    [SN-1:0]        exist_a_slot;
    wire    [SN-1:0]        exist_b_slot;
    wire                    exist_a;
    wire                    exist_b;

    //insert or delete
    wire                    ram_wr;
    wire    [HW-1:0]        ram_wr_addr;
    wire    [(DW+1)*SN-1:0] ram_wr_data_insert;
    wire    [(DW+1)*SN-1:0] ram_wr_data_delete;
    wire    [(DW+1)*SN-1:0] ram_wr_data;

    //output
    reg                     insert_error;
    reg                     insert_end;

    reg                     delete_error;
    reg                     delete_end;

    //---------------Processing------------------
    //register
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            // reset
            insert_r        <=  1'b0;
            insert_rr       <=  1'b0;
            delete_r        <=  1'b0;
            delete_rr       <=  1'b0;
            search_a_r      <=  1'b0;
            search_a_rr     <=  1'b0;
            search_b_r      <=  1'b0;
            search_b_rr     <=  1'b0;
        end
        else begin
            insert_r        <=  insert_i;
            insert_rr       <=  insert_r;
            delete_r        <=  delete_i;
            delete_rr       <=  delete_r;
            search_a_r      <=  search_a_i;
            search_a_rr     <=  search_a_r;
            search_b_r      <=  search_b_i;
            search_b_rr     <=  search_b_r;
        end
    end

    //step 0: hash calculate
    //for insert/delete/search_a
    assign  hash_a_valid    =   insert_i | delete_i | search_a_i;
    assign  hash_a_data     =   insert_i?insert_data_i:
                                delete_i?delete_data_i:
                                search_a_i?search_a_data_i:{DW{1'h0}};

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            // reset
            hash_a_data_r   <=  {DW{1'b0}};
            hash_a_data_rr  <=  {DW{1'b0}};
        end
        else begin
            hash_a_data_r   <=  hash_a_data;
            hash_a_data_rr  <=  hash_a_data_r;
        end
    end

    CRC32_D32 #(
        .HW                 (HW),
        .HIGH               (1)
    ) U0_CRC32_D32(
        .clk                (clk),
        .rst_n              (rst_n),

        .calc_data_i        (hash_a_data),
        .calc_en_i          (hash_a_valid),

        .crc32_o            (hash_a_result),
        .crc32_valid_o      (hash_a_result_valid)
    );

    always @ (posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            hash_a_result_r     <=  {HW{1'b0}};
        end
        else begin
            hash_a_result_r     <=  hash_a_result;
        end
    end

    //for insert/delete/search_a
    assign  hash_b_valid    =   search_b_i;
    assign  hash_b_data     =   search_b_i?search_b_data_i:{DW{1'h0}};

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            // reset
            hash_b_data_r   <=  {DW{1'b0}};
            hash_b_data_rr  <=  {DW{1'b0}};
        end
        else begin
            hash_b_data_r   <=  hash_b_data;
            hash_b_data_rr  <=  hash_b_data_r;
        end
    end

    CRC32_D32 #(
        .HW                 (HW),
        .HIGH               (1)
    ) U1_CRC32_D32(
        .clk                (clk),
        .rst_n              (rst_n),

        .calc_data_i        (hash_b_data),
        .calc_en_i          (hash_b_valid),

        .crc32_o            (hash_b_result),
        .crc32_valid_o      (hash_b_result_valid)
    );

    //step 1: search hash table
    assign  ram_rd_addra        =   hash_a_result;
    assign  ram_rd_addrb        =   hash_b_result;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            // reset
            ram_rd_dataa_valid  <=  1'b0;
            ram_rd_datab_valid  <=  1'b0;
        end
        else begin
            ram_rd_dataa_valid  <=  hash_a_result_valid;
            ram_rd_datab_valid  <=  hash_b_result_valid;
        end
    end

    //insert: confirm blank slot
    generate
        genvar i;
        for (i=0;i<SN;i=i+1) begin:confirm_blank_slot
            assign  blank_slot[i]   =   ram_rd_dataa_valid & (~ram_douta_i[(DW+1)*(i+1)-1]);
        end
    endgenerate

    assign  double_blank_slot       =   {blank_slot,blank_slot};
    assign  double_blank_slot_temp  =   double_blank_slot & (~(double_blank_slot - {{(2*SN-1){1'b0}},{1'b1}}));
    assign  min_blank_slot          =   (double_blank_slot_temp[2*SN-1:SN] | double_blank_slot_temp[SN-1:0]) & (~{SN{exist_a}});

    //delete: confirm exist slot
    generate
        genvar j;
        for (j=0;j<SN;j=j+1) begin:confirm_exist_slot
            assign  exist_a_slot[j] =   ram_rd_dataa_valid & (ram_douta_i[(DW+1)*(j+1)-1]) & (hash_a_data_rr == ram_douta_i[(DW+1)*(j+1)-2:(DW+1)*j]);
            assign  exist_b_slot[j] =   ram_rd_datab_valid & (ram_doutb_i[(DW+1)*(j+1)-1]) & (hash_b_data_rr == ram_doutb_i[(DW+1)*(j+1)-2:(DW+1)*j]);
        end
    endgenerate

    //search: confirm exist
    assign  exist_a             =   |exist_a_slot;
    assign  exist_b             =   |exist_b_slot;

    //step 2: insert or delete
    assign  ram_wr              =   (insert_rr & (|min_blank_slot)) | (delete_rr & exist_a);
    assign  ram_wr_addr         =   hash_a_result_r;

    generate
        genvar k;
        for (k=0;k<SN;k=k+1) begin:gen_ram_wr_data
            assign  ram_wr_data_insert[(DW+1)*(k+1)-1:(DW+1)*k] =   insert_rr && min_blank_slot[k]?{1'b1,hash_a_data_rr}:ram_douta_i[(DW+1)*(k+1)-1:(DW+1)*k];
            assign  ram_wr_data_delete[(DW+1)*(k+1)-1:(DW+1)*k] =   delete_rr && exist_a_slot[k]?{1'b0,{DW{1'b0}}}:ram_douta_i[(DW+1)*(k+1)-1:(DW+1)*k];
        end
    endgenerate

    assign  ram_wr_data         =   (delete_rr & exist_a)?ram_wr_data_delete:ram_wr_data_insert;

    //------------Output Signals-----------------
    //insert
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            // reset
            insert_error        <=  1'b0;
            insert_end          <=  1'b0;
        end
        else begin
            insert_error        <=  insert_rr && (~(|min_blank_slot));
            insert_end          <=  insert_rr;
        end
    end

    assign  insert_error_o      =   insert_error;
    assign  insert_end_o        =   insert_end;

    //delete
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            // reset
            delete_error        <=  1'b0;
            delete_end          <=  1'b0;
        end
        else begin
            delete_error        <=  delete_rr && (~exist_a);
            delete_end          <=  delete_rr;
        end
    end

    assign  delete_error_o      =   delete_error;
    assign  delete_end_o        =   delete_end;

    //search
    assign  search_a_exist_o    =   search_a_rr && exist_a;
    assign  search_a_end_o      =   search_a_rr;

    assign  search_b_exist_o    =   search_b_rr && exist_b;
    assign  search_b_end_o      =   search_b_rr;

    //RAM
    assign  ram_wea_o           =   1'b0;
    assign  ram_addra_o         =   ram_rd_addra;
    assign  ram_dina_o          =   {((DW+1)*SN){1'b0}};

    assign  ram_web_o           =   ram_wr;
    assign  ram_addrb_o         =   ram_wr?ram_wr_addr:ram_rd_addrb;
    assign  ram_dinb_o          =   ram_wr?ram_wr_data:{((DW+1)*SN){1'b0}};

    //------------Debug Signals------------------
endmodule
